﻿using Sasa.QualityTools.CodeAnalysis.Common;
using Sasa.QualityTools.CodeAnalysis.SearchEngine.Processing;
using Sasa.QualityTools.CodeAnalysis.SearchEngine.Processing.Tokens;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Sasa.QualityTools.CodeAnalysis.SearchEngine
{
    public class QueryCompiler<T>
    {
        IWordCompiler<T> wordCompiler;


        public QueryCompiler(IWordCompiler<T> wordCompiler)
        {
            if (wordCompiler == null)
            {
                throw new ArgumentNullException("wordCompiler");
            }
            this.wordCompiler = wordCompiler;
        }


        public Func<T, bool> Compile(string query)
        {
            Logger.Info("Began to compile query '{0}'.", query);

            var tokenizer = new Tokenizer();
            var preprocessor = new Preprocessor();
            var parser = new Parser();
            var tokens = parser.Parse(preprocessor.Process(tokenizer.Tokenize(query)));
            var result = (tokens.Count() != 0) ? Compile(tokens) : (T obj) => true;

            Logger.Info("Ended to compile query '{0}'.", query);

            return result;
        }

        private Func<T, bool> Compile(IEnumerable<IToken> tokens)
        {
            var stack = new Stack<Func<T, bool>>();
            foreach (var token in tokens)
            {
                if (token is And)
                {
                    try
                    {
                        var right = stack.Pop();
                        var left = stack.Pop();
                        stack.Push((T obj) => left(obj) && right(obj));
                    }
                    catch (InvalidOperationException ex)
                    {
                        Logger.Error(ex, "The specified tokens sequence is not correct reverse polish notation.");
                        throw new InvalidOperationException("The specified tokens sequence is not correct reverse polish notation.", ex);
                    }
                }
                else if (token is Or)
                {
                    try
                    {
                        var right = stack.Pop();
                        var left = stack.Pop();
                        stack.Push((T obj) => left(obj) || right(obj));
                    }
                    catch (InvalidOperationException ex)
                    {
                        Logger.Error(ex, "The specified tokens sequence is not correct reverse polish notation.");
                        throw new InvalidOperationException("The specified tokens sequence is not correct reverse polish notation.", ex);
                    }
                }
                else
                {
                    stack.Push(this.wordCompiler.Compile(token.Value));
                }
            }

            if (stack.Count != 1)
            {
                Logger.Error("The specified tokens sequence is not correct reverse polish notation.");
                throw new InvalidOperationException("The specified tokens sequence is not correct reverse polish notation.");
            }

            return stack.Pop();
        }
    }
}
